// 对接销售易的CRM系统
// auth：翁果

package neocrm

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"git.mycaigou.com/gfyx/common/http_client"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/constants"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/clue/crm/contract"
	gfyx_crm_log2 "git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/repository/clue/crm/gfyx_crm_log"
	"git.myscrm.cn/golang/common/ykenv"
	"git.myscrm.cn/golang/stark/v4"
	"github.com/google/uuid"
	"strings"
	"sync"
	"time"
)

// todo 这些都记录到配置数据库中去
const (
	DefaultEntityType   = 11010001100001   // 对方业务系统默认的业务类型ID
	DefaultLeadSourceId = 2810441068937868 //与对方系统约定的明源云线索雷达的来源ID
	AuthUrl             = "https://login.xiaoshouyi.com/auc/oauth2/token"
	ClientId            = "5673f3da727bb0dc5212b9b25896122a"
	ClientSecret        = "4561e2821e49aea72bfe5c17d84b1c76"
	Username            = "13168535643"
	Password            = "Zs1236548UOB1b9p"

	DescriptionClueUri = "/rest/data/v2.0/xobjects/lead/description"
	CreateClueUri      = "/rest/data/v2.0/xobjects/lead"
	XOQLUri            = "/rest/data/v2.0/query/xoql"
)

type neoCrmToken struct {
	lock       sync.RWMutex
	expireTime time.Time

	AccessToken   string `json:"access_token"`
	TokenType     string `json:"token_type"`
	ExpiresIn     int    `json:"expires_in"`
	Scope         string `json:"scope"`
	TenantId      string `json:"tenant_id"`
	LoginType     string `json:"login_type"`
	EncryptionKey string `json:"encryption_key"`
	ClientId      string `json:"client_id"`
	PassportId    string `json:"passport_id"`
	MobileParams  string `json:"mobile_params"`
	InstanceUri   string `json:"instance_uri"`
}

type neoCrmConfig struct {
	clientId     string
	clientSecret string
	username     string
	password     string
}

type neoCrmArea struct {
	lock                  sync.RWMutex
	ProvinceName2Code     map[string]int32
	CityName2Code         map[string]int32
	CityCode2ProvinceCode map[int32]int32
}

type neoCrmOwner struct {
	lock           sync.RWMutex
	mobile2OwnerId map[string]string
}

type NeoCrmRepository struct {
	httpClient *http_client.HttpClientTool
	logRepo    gfyx_crm_log2.GfyxCrmLogRepositoryIface
	config     neoCrmConfig
	token      neoCrmToken
	areaMap    neoCrmArea
	owner      neoCrmOwner
}

// NewNeoCrmRepository 构造函数
func NewNeoCrmRepository() contract.ThirdCrmIface {
	return &NeoCrmRepository{
		httpClient: http_client.New(),
		config: neoCrmConfig{
			clientId:     ClientId,
			clientSecret: ClientSecret,
			username:     Username,
			password:     Password,
		},
		areaMap: neoCrmArea{
			ProvinceName2Code:     make(map[string]int32),
			CityName2Code:         make(map[string]int32),
			CityCode2ProvinceCode: make(map[int32]int32),
		},
		owner:   neoCrmOwner{mobile2OwnerId: make(map[string]string)},
		logRepo: gfyx_crm_log2.NewGfyxCrmLogRepos(),
	}
}

// CreateClue 创建第三方crm系统的线索，返回线索id
func (r *NeoCrmRepository) CreateClue(ctx context.Context, crmData *contract.CrmData, opt *contract.Operation) (string, error) {
	//1.参数校验
	if crmData == nil {
		return "", fmt.Errorf("参数不能为空")
	}

	//2.获取token
	accessToken, err := r.getToken(ctx)
	if err != nil {
		return "", err
	}
	//3.发起请求,先发起一次description接口，获取省、市的编码信息
	r.getAreaName2CodeMapping(ctx, accessToken)

	// 4.构建请求参数
	reqParam := r.buildParam(ctx, crmData)

	resp := leadCreatedResponse{}
	url := "https://" + r.token.InstanceUri + CreateClueUri
	params := map[string]interface{}{"data": reqParam}
	header := map[string]string{"Authorization": accessToken}

	st := time.Now()
	_, respBytes, errs := r.httpClient.Post(ctx, url, params, header).EndStruct(&resp)
	spendTime := time.Now().Sub(st).Milliseconds()
	// 5.记录请求日志
	var status int32 = 0
	if len(errs) != 0 || resp.Code != "200" {
		status = 1
	}
	headerBytes, _ := json.Marshal(header)
	body, _ := json.Marshal(params)
	err = r.logRepo.Save(ctx, &gfyx_crm_log2.GfyxCrmLogPo{
		Uid:          uuid.New().String(),
		BusinessUid:  crmData.BusinessUid,
		BusinessType: crmData.BusinessType,
		Url:          url,
		Header:       string(headerBytes),
		Body:         string(body),
		Response:     string(respBytes),
		SpendTime:    spendTime,
		Status:       status,
		RetryTimes:   0,
		RetryTime:    time.Now(),
		CreatedOn:    time.Now(),
		CreatedBy:    "",
		ModifiedOn:   time.Now(),
		ModifiedBy:   "",
		IsDeleted:    constants.IsDeletedFalse,
	})
	stark.Logger.Infof(ctx, "CreateClue url:%s, params:%#v, header:%#v, resp:%s, spend time:%d ms", url, params, header, string(respBytes), spendTime)

	if resp.Code != "200" {
		return "", errors.New(resp.Msg)
	}

	return fmt.Sprintf("%d", resp.Data.Id), nil
}

func (r *NeoCrmRepository) getAreaName2CodeMapping(ctx context.Context, accessToken string) {
	if len(r.areaMap.ProvinceName2Code) != 0 && len(r.areaMap.CityName2Code) != 0 {
		return
	}

	r.areaMap.lock.Lock()
	defer r.areaMap.lock.Unlock()

	resp := clueDescriptionResponse{}
	url := "https://" + r.token.InstanceUri + DescriptionClueUri
	header := map[string]string{"Authorization": accessToken}

	_, respBytes, errs := r.httpClient.Get(ctx, url, header).EndStruct(&resp)
	if len(errs) != 0 {
		fmt.Printf("getAreaName2CodeMapping resp:%s errs:%+v\n", string(respBytes), errs)
		stark.Logger.Errorf(ctx, "getAreaName2CodeMapping resp:%s errs:%+v", string(respBytes), errs)
	} else {
		for _, field := range resp.Data.Fields {
			if field.ApiKey == "fState" {
				for _, item := range field.Selectitem {
					//
					r.areaMap.ProvinceName2Code[item.Label] = item.Value
					r.areaMap.ProvinceName2Code[strings.ReplaceAll(item.Label, "省", "")] = item.Value
				}
			}
			if field.ApiKey == "fCity" {
				for _, item := range field.Selectitem {
					r.areaMap.CityName2Code[item.Label] = item.Value
					r.areaMap.CityName2Code[strings.ReplaceAll(item.Label, "市", "")] = item.Value
					if len(item.DependentValue) > 0 {
						//城市code和其对应的省份code
						r.areaMap.CityCode2ProvinceCode[item.Value] = int32(item.DependentValue[0])
					}
				}
			}
		}
	}

	return
}

func (r *NeoCrmRepository) getConfig(ctx context.Context) map[string]interface{} {
	//todo 从配置表中获取配置，更新到内存

	m := map[string]interface{}{
		"grant_type":    "password",
		"client_id":     r.config.clientId,
		"client_secret": r.config.clientSecret,
		"username":      r.config.username,
		"password":      r.config.password,
	}
	return m
}

func (r *NeoCrmRepository) refreshToken(ctx context.Context) (string, error) {
	r.token.lock.Lock()
	defer r.token.lock.Unlock()

	// 请求第三方的接口获取token
	params := r.getConfig(ctx)

	_, _, errs := r.httpClient.Post(ctx, AuthUrl, params, map[string]string{"Content-Type": "application/x-www-form-urlencoded"}).EndStruct(&r.token)
	if len(errs) != 0 {
		return "", errs[0]
	}

	// 更新缓存有效期
	r.token.expireTime = time.Now().Add(time.Duration(r.token.ExpiresIn) * time.Second)

	return fmt.Sprintf("%s %s", r.token.TokenType, r.token.AccessToken), nil
}

func (r *NeoCrmRepository) getToken(ctx context.Context) (string, error) {
	// 优先取内存token
	if r.token.TokenType != "" && r.token.AccessToken != "" && time.Now().Before(r.token.expireTime) {
		return fmt.Sprintf("%s %s", r.token.TokenType, r.token.AccessToken), nil
	}
	// 内存token不存在，或已过期，调用接口重新获取
	return r.refreshToken(ctx)
}

func (r *NeoCrmRepository) buildParam(ctx context.Context, crmData *contract.CrmData) map[string]interface{} {
	if crmData == nil {
		return nil
	}
	result := make(map[string]interface{})

	// 非生产环境数据，加上测试前缀
	name := crmData.UserName
	if !ykenv.IsProdMode() {
		name = "明源云测试-" + name
	}

	responsibleUserMobile := crmData.ResponsiblePersonMobile
	if !ykenv.IsProdMode() {
		// 测试环境指定手机号
		responsibleUserMobile = "13620842454"
	}
	if responsibleUserMobile != "" {
		ownerId, _ := r.getOwnerIdByMobile(ctx, responsibleUserMobile)
		if ownerId != "" {
			result["ownerId"] = ownerId
		}
	}

	result["entityType"] = DefaultEntityType
	result["leadSourceId"] = DefaultLeadSourceId
	result["createdAt"] = time.Now().UnixMilli()
	result["name"] = name
	result["companyName"] = crmData.CompanyName
	result["mobile"] = crmData.UserMobile
	result["phone"] = crmData.UserMobile
	result["post"] = crmData.UserPosition

	//根据省市名称转换省市code
	if provinceCode, ok := r.areaMap.ProvinceName2Code[crmData.Province]; ok {
		result["fState"] = provinceCode
	}
	if cityCode, ok := r.areaMap.CityName2Code[crmData.City]; ok {
		result["fCity"] = cityCode
		if _, ok1 := result["fState"]; !ok1 {
			//省份code没查到，就用城市code的父级code再查一次
			if provinceCode, ok2 := r.areaMap.CityCode2ProvinceCode[cityCode]; ok2 {
				result["fState"] = provinceCode
			}
		}
	}

	return result
}

func (r *NeoCrmRepository) getOwnerIdByMobile(ctx context.Context, mobile string) (string, error) {
	if ownerId, ok := r.owner.mobile2OwnerId[mobile]; ok {
		return ownerId, nil
	}

	r.owner.lock.Lock()
	defer r.owner.lock.Unlock()

	// 请求第三方的接口获取token
	accessToken, err := r.getToken(ctx)
	if err != nil {
		return "", err
	}
	resp := xoqlResponse{}
	url := "https://" + r.token.InstanceUri + XOQLUri
	header := map[string]string{"Authorization": accessToken, "Content-Type": "application/x-www-form-urlencoded"}

	_, respBytes, errs := r.httpClient.Post(ctx, url,
		map[string]interface{}{"xoql": fmt.Sprintf("select id,phone from user where phone = '%s'", mobile)},
		header).EndStruct(&resp)
	if len(errs) != 0 {
		fmt.Printf("getOwnerIdByMobile resp:%s errs:%+v\n", string(respBytes), errs)
		stark.Logger.Errorf(ctx, "getOwnerIdByMobile resp:%s errs:%+v", string(respBytes), errs)
	} else {
		for _, record := range resp.Data.Records {
			if record.Id != "" && record.Phone != "" {
				//保存到内存中
				r.owner.mobile2OwnerId[record.Phone] = record.Id
			}
		}
	}

	// 重新从内存获取
	if ownerId, ok := r.owner.mobile2OwnerId[mobile]; ok {
		return ownerId, nil
	}
	return "", nil
}
